#pragma once

#include <string>
#include <stack>
#include <set>
#include <llvm/ADT/SmallVector.h>
#include <llvm/ADT/SmallSet.h>
#include <llvm/ADT/StringMap.h>
#include <optional>

class Scope;
class PrototypeAST;
namespace llvm {
    class Value;
    class Type;
    class StructType;
    class BasicBlock;
} // namespace llvm

class StructDef {
  private:
    llvm::StringMap<unsigned int> fieldIndex;
    unsigned int indexCount = 0;

  public:
    const std::string name;
    llvm::StructType *const type;

    std::optional<unsigned int> findIndex(const std::string& field) const;

    StructDef(std::string name, llvm::StructType *type);

    void addField(const std::string& field);

    bool operator==(const StructDef& rhs) const {
        return name == rhs.name && type == rhs.type;
    }
};

class ScopeGuard final {
  private:
    Scope &scope;

  public:
    ScopeGuard(Scope &scope);
    ScopeGuard(const ScopeGuard &) = delete;
    ScopeGuard(ScopeGuard &&) = delete;
    ScopeGuard &operator=(const ScopeGuard &) = delete;
    ~ScopeGuard();
};

class Scope final {
  private:
    using NameVector = llvm::SmallVector<std::string, 8>;
    using NameSet = llvm::SmallSet<std::string, 8>;
    using DeletedValue =
        llvm::SmallVector<std::pair<std::string, llvm::Value *>>;
    using BreakPoint = llvm::BasicBlock *;
    using ContinuePoint = llvm::BasicBlock *;
    // using NameTuple = std::
    //     tuple<NameVector, DeletedValue, NameSet, BreakPoint, ContinuePoint>;

    struct ScopeInfo {
        NameVector nameVector;
        DeletedValue deletedValues;
        NameSet nameSet;
        BreakPoint breakPoint;
        ContinuePoint continuePoint;
    };

    using NamesStack = std::stack<ScopeInfo>;
    using ValueTable = llvm::StringMap<llvm::Value *>;
    using PrototypeTable = llvm::StringMap<PrototypeAST *>;
    using StructTypeTable = llvm::StringMap<StructDef>;

    NamesStack scopeStack;
    ValueTable namedValuePtr;
    PrototypeTable protos;
    StructTypeTable structs;

  public:
    // 新建一层作用域
    void push();

    // 退出当前作用域
    void pop();

    // 新建一个局部变量
    bool insertValue(const std::string &name, llvm::Value *value);

    // 获取局部变量的地址
    llvm::Value *findValue(const std::string &name) const;

    // RAII 新建/退出作用域
    ScopeGuard guard();

    // 栈、符号表是否为空。true => 位于全局空间
    bool isClear() const;

    // 新建一个函数声明
    bool insertProto(const std::string &name, PrototypeAST *proto);

    // 获取一个函数声明
    const PrototypeAST *findProto(const std::string &name) const;

    // 新建一个结构体定义
    bool insertStruct(const std::string &name, StructDef&& structDef);

    // 是否存在一个结构体定义
    bool hasStruct(const std::string& name);

    // 获取一个结构体定义
    const StructDef &findStruct(const std::string &name) const;

    // 获取一个自定义类型（尤指struct）
    llvm::Type* findType(const std::string& typeName) const;

    // 标记当前作用域的 break 锚点
    void setBreakPoint(const BreakPoint block);

    // 获取当前作用域的 break 锚点
    BreakPoint getBreakPoint() const;

    // 标记当前作用域的 continue 锚点
    void setContinuePoint(const ContinuePoint block);

    // 获取当前作用域的 continue 锚点
    ContinuePoint getContinuePoint() const;
};
